In [1]:
%%HTML
<style> 
div#notebook-container.container {
  /* This acts as a spacer between cells,
     that is outside the border */
  margin: 2px 0px 2px 0px;
  list-style: none;
  padding: 0;
  margin: 0;
  -ms-box-orient: horizontal;
  display: -webkit-box;
  display: -moz-box;
  display: -ms-flexbox;
  display: -moz-flex;
  display: -webkit-flex;
  display: flex;  
  justify-content: space-around;
  -webkit-flex-wrap: wrap;
  flex-wrap: wrap;
  -webkit-flex-direction: row;
  flex-direction: row;
}
div.cell {
width:550px
}
  </style>


The code on the left is a hack to make this notebook two-column. I found it here:

http://stackoverflow.com/questions/23370670/ipython-notebook-put-code-cells-into-columns

Object Oriented Programming (OOP) in Python

Welcome to the OOP-session of our Python course! This notebook introduces Python's OOP-concepts in a two column-style side by side with an equivalent formulation in Java respectively.

We chose Java here, as it is a popular OOP-enabled language that many of you are probably familiar with. Also, we found it helpful to compare Python-style to some other language and Java-OOP is still somewhat easier to read than OOP in C-ish languages.

Basic class with constructor etc

Python

 

Java

class SpaceShip(SpaceObject):
    bgColor = (0, 0, 0, 0)

    def __init__(color, position):
        super(SpaceShip, self).__init__(
                position)
        self.color = color

    def fly(self, moveVector):
        self.position += moveVector

    @staticmethod
    def get_bgColor():
        return SpaceShip.bgColor

See https://julien.danjou.info/blog/2013/guide-python-static-class-abstract-methods for a guide about the decorators @staticmethod, @classmethod and abstractmethod.

Classmethods have no equivalent in Java. They are like special static methods that get the class as their initial, implicit argument:

@classmethod
    def get_bgColor(cls):
        return cls.bgColor
public class SpaceShip extends SpaceObject {
    public static Color bgColor =
            new Color(0, 0, 0, 0);
    public Color color;

    public SpaceShip(Color col, Vec3D pos) {
        super(pos);
        color = col;
    }

    public void fly(Vec3D move) {
        position.add(move);
    }

    public static Color get_bgColor() {
        return bgColor;
    }
}

Abstract classes

from abc import ABCMeta

class Target():
    __metaclass__ = ABCMeta

    @abstractmethod
    def hit(self, strength):
        pass
public interface Target {
  public void hit(double strength);
}
//or
public abstract class Target {
  public abstract void hit(double strength);
}

Multiple inheritance

class SpaceShip(SpaceObject, Target):

    def hit(self, strength):
        print "Damn I'm hit."
public class SpaceShip extends SpaceObject 
    implements Target {

  public void hit(double strength) {
    System.out.println("Damn I'm hit.");
  }
}
class Hitpoints(Target):
    def __init__(self):
        self.hitpoints = 100

    def hit(self, strength):
        self.hitpoints -= strength


class SpaceShip(SpaceObject, Hitpoints):
    def __init__(self):
        Hitpoints.__init__(self)
        super(SpaceShip, self).__init__()
public class HitpointSpaceShip extends 
    SpaceShip implements Hitpoints {
  double hitpoints = 100.0;
}

public interface Hitpoints extends Target {
  //Java 8 introduced default-implementations:
  default void hit(double strength) {
    ((HitpointSpaceShip) this).hitpoints -=
        strength;
  }
}

Overloading operators

class Fraction():

  def __init__(self, numerator,denominator):
    self.num = numerator
    self.den = denominator

  def __mul__(self, other):
    return Fraction(self.num * other.num,
        self.den * other.den)

Task:

Implement numerical and logical magic methods. (How many can you get done in the available time?)

Also consider the idea that numerator and denominator are functions, e.g. numpy.polynomial.polynomial. In this case Fraction shall also act as a function. How can you achieve this?

New-style classes

Classic class:

Original essay about new-style classes by Guido van Rossum: https://www.python.org/download/releases/2.2.3/descrintro/

New-style class:


In [31]:
class classA():
    pass

a = classA()

print type(a)


<type 'instance'>

In [32]:
class classA(object):
    pass

a = classA()

print type(a)


<class '__main__.classA'>

Extending built-in types

Note that multi-inheritence is constrained to one built-in type only. You cannot extend multiple built-in types.


In [33]:
class evenInt(int):

    def __init__(self, value):
        if value % 2 != 0:
            raise ValueError(str(value)+
                             ' is not even')
        super(evenInt, self).__init__(value)

a = evenInt(24)
b = 9

a+b


Out[33]:
33

In [30]:
class defaultdict(dict):

    def __init__(self, default=None):
        dict.__init__(self)
        self.default = default

    def __getitem__(self, key):
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            return self.default

a = defaultdict(default=0.0)

print a
a['x1'] = 1
print a['x1']
print a
print a['x2']
a.y = '7'
print a.y
print a.__dict__


{}
1
{'x1': 1}
0.0
7
{'default': 0.0, 'y': '7'}

Slots

Slots can replace the mutable class-dictionary __dict__ by a fixed data structure. __slots__ disallows adding or removing attributes to a class. Its main purpose is to avoid the need of lots of dictionaries when something simple like int is subclassed.

Basic slot-example:

You cannot modify slots afterwards (well, you can, but it doesn't add the attribute):


In [3]:
class defaultdict(dict):

    __slots__ = ['default']

    def __init__(self, default=None):
        dict.__init__(self)
        self.default = default

    def __getitem__(self, key):
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            return self.default

a = defaultdict(default=0.0)

print a
a['x1'] = 1
print a['x1']
print a
print a['x2']
#a.y = '7'
#print a.y
#print a.__dict__
print a.__slots__


{}
1
{'x1': 1}
0.0
['default']

In [2]:
class defaultdict(dict):

    __slots__ = ['default']

    def __init__(self, default=None):
        dict.__init__(self)
        self.default = default

    def __getitem__(self, key):
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            return self.default

a = defaultdict(default=0.0)

print a
a['x1'] = 1
print a['x1']
print a
print a['x2']
#a.y = '7'
#print a.y
#print a.__dict__
print a.__slots__
a.__slots__.append('y')
print a.__slots__
a.y = '7'
print a.y


{}
1
{'x1': 1}
0.0
['default']
['default', 'y']
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-2-646473a830b1> in <module>()
     26 a.__slots__.append('y')
     27 print a.__slots__
---> 28 a.y = '7'
     29 print a.y

AttributeError: 'defaultdict' object has no attribute 'y'

Some notes on slots:

  • cannot be used in not-only-__slot__ subclasses
  • There's no check to prevent name conflicts between the slots defined in a class and the slots defined in its base classes
  • You cannot use slots with "variable-length" built-in types as base class. Variable-length built-in types are long, str and tuple.

In [ ]: